package service

import (
	"context"
	"github.com/ecodeclub/ekit/slice"
	"golang.org/x/sync/errgroup"
	followv1 "jk-time/webook/api/proto/gen/follow/v1"
	"jk-time/webook/feed/domain"
	"jk-time/webook/feed/repository"
	"sort"
	"sync"
	"time"
)

const (
	ArticleEventName = "article_event"
	threshold        = 5
)

type ArticleEventService struct {
	repo         repository.FeedRepository
	followClient followv1.FollowServiceClient
}

func NewArticleEventService(repo repository.FeedRepository, followClient followv1.FollowServiceClient) Handler {
	return &ArticleEventService{repo: repo, followClient: followClient}
}

func (f ArticleEventService) CreateHandleEvent(ctx context.Context, ext domain.ExtendFields) error {
	uid, err := ext.Get("uid").Int64()
	if err != nil {
		return err
	}
	resp, err := f.followClient.GetFollowee(ctx, &followv1.GetFolloweeRequest{
		Follower: uid,
		Limit:    100000,
	})
	if err != nil {
		return err
	}
	// 读扩散:如果粉丝>num，写入自己的发件箱
	if len(resp.FollowRelations) > threshold {
		resp0 := resp.FollowRelations[0]
		return f.repo.CreatePullEvents(ctx, domain.FeedEvent{
			Uid:   resp0.Follower,
			Type:  ArticleEventName,
			Ctime: time.Now(),
			Ext:   ext,
		})
	}
	// 写扩散
	fes := slice.Map(resp.FollowRelations, func(idx int, src *followv1.FollowRelation) domain.FeedEvent {
		return domain.FeedEvent{
			Uid:   src.Follower,
			Type:  ArticleEventName,
			Ctime: time.Now(),
			Ext:   ext,
		}
	})
	return f.repo.CreatePushEvents(ctx, fes)
}
func (f ArticleEventService) CreateHandleHotEvent(ctx context.Context, ext domain.ExtendFields) error {
	uid, err := ext.Get("uid").Int64()
	if err != nil {
		return err
	}
	resp, err := f.followClient.GetFollowee(ctx, &followv1.GetFolloweeRequest{
		Follower: uid,
		Limit:    100000,
	})
	if err != nil {
		return err
	}
	// 读扩散:如果粉丝>num
	if len(resp.FollowRelations) > threshold {
		//判断有没有热点用户
		events := slice.FilterMap(resp.FollowRelations, func(idx int, src *followv1.FollowRelation) (domain.FeedEvent, bool) {
			if !f.isActiveUse(src.Follower) {
				return domain.FeedEvent{}, false
			}
			return domain.FeedEvent{Uid: src.Follower, Ctime: time.Now(), Type: ArticleEventName, Ext: ext}, true
		})
		if err := f.repo.CreatePushEvents(ctx, events); err != nil {
			return err
		}
		resp0 := resp.FollowRelations[0]
		return f.repo.CreatePullEvents(ctx, domain.FeedEvent{
			Uid:   resp0.Follower,
			Type:  ArticleEventName,
			Ctime: time.Now(),
			Ext:   ext,
		})
	}
	// 写扩散
	fes := slice.Map(resp.FollowRelations, func(idx int, src *followv1.FollowRelation) domain.FeedEvent {
		return domain.FeedEvent{
			Uid:   src.Follower,
			Type:  ArticleEventName,
			Ctime: time.Now(),
			Ext:   ext,
		}
	})
	return f.repo.CreatePushEvents(ctx, fes)
}

// FindHandleEvents 找自己的消息
func (f ArticleEventService) FindHandleEvents(ctx context.Context, uid, timestamp, limit int64) ([]domain.FeedEvent, error) {
	var (
		res  = make([]domain.FeedEvent, 0, limit*2)
		eg   errgroup.Group
		lock sync.Mutex
	)
	// 找被关注者发件箱
	eg.Go(func() error {
		if f.isActiveUse(uid) {
			return nil
		}
		resp, err := f.followClient.GetFollowee(ctx, &followv1.GetFolloweeRequest{
			Follower: uid,
			Limit:    100,
		})
		if err != nil {
			return err
		}
		followers := make([]int64, len(resp.FollowRelations))
		for i, fr := range resp.FollowRelations {
			followers[i] = fr.Follower
		}

		pulls, err := f.repo.FindPullEventListWithTyp(ctx, ArticleEventName, followers, timestamp, limit)
		if err != nil {
			return err
		}
		lock.Lock()
		res = append(res, pulls...)
		lock.Unlock()
		return nil
	})
	// 找自己收件箱
	eg.Go(func() error {
		pushs, err := f.repo.FindPushEventListWithTyp(ctx, ArticleEventName, uid, timestamp, limit)
		if err != nil {
			return err
		}
		lock.Lock()
		res = append(res, pushs...)
		lock.Unlock()
		return nil
	})

	err := eg.Wait()
	if err != nil {
		return nil, err
	}

	//排序
	sort.Slice(res, func(i, j int) bool {
		return res[i].Ctime.UnixMilli() > res[j].Ctime.UnixMilli()
	})
	return res[:slice.Min[int]([]int{int(limit), len(res)})], nil
}
func (f ArticleEventService) isActiveUse(uid int64) bool {
	return true
}
